<!DOCTYPE html>
<html lang="en">
<head>
	<title>three.js webgl - indexed instancing (single box), interleaved buffers</title>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
	<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>

	<div id="container"></div>
	<div id="info">
		<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - indexed instancing (single box), interleaved buffers
		<div id="notSupported" style="display:none">Sorry your graphics card + browser does not support hardware instancing</div>
	</div>

	<script type="importmap">
		{
			"imports": {
				"three": "../build/three.module.js",
				"three/addons/": "./jsm/"
			}
		}
	</script>

	<script type="module">
		import * as THREE from 'three';

		import Stats from 'three/addons/libs/stats.module.js';

		let container, stats;
		let camera, scene, renderer, mesh;

		const instances = 5000;
		let lastTime = 0;

		const moveQ = new THREE.Quaternion( 0.5, 0.5, 0.5, 0.0 ).normalize();
		const tmpQ = new THREE.Quaternion();
		const tmpM = new THREE.Matrix4();
		const currentM = new THREE.Matrix4();

		init();

		function init() {

			container = document.getElementById( 'container' );

			camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );

			scene = new THREE.Scene();
			scene.background = new THREE.Color( 0x101010 );

			// geometry

			const geometry = new THREE.InstancedBufferGeometry();

			// per mesh data x,y,z,w,u,v,s,t for 4-element alignment
			// only use x,y,z and u,v; but x, y, z, nx, ny, nz, u, v would be a good layout
			const vertexBuffer = new THREE.InterleavedBuffer( new Float32Array( [
				// Front
				- 1, 1, 1, 0, 0, 0, 0, 0,
				1, 1, 1, 0, 1, 0, 0, 0,
				- 1, - 1, 1, 0, 0, 1, 0, 0,
				1, - 1, 1, 0, 1, 1, 0, 0,
				// Back
				1, 1, - 1, 0, 1, 0, 0, 0,
				- 1, 1, - 1, 0, 0, 0, 0, 0,
				1, - 1, - 1, 0, 1, 1, 0, 0,
				- 1, - 1, - 1, 0, 0, 1, 0, 0,
				// Left
				- 1, 1, - 1, 0, 1, 1, 0, 0,
				- 1, 1, 1, 0, 1, 0, 0, 0,
				- 1, - 1, - 1, 0, 0, 1, 0, 0,
				- 1, - 1, 1, 0, 0, 0, 0, 0,
				// Right
				1, 1, 1, 0, 1, 0, 0, 0,
				1, 1, - 1, 0, 1, 1, 0, 0,
				1, - 1, 1, 0, 0, 0, 0, 0,
				1, - 1, - 1, 0, 0, 1, 0, 0,
				// Top
				- 1, 1, 1, 0, 0, 0, 0, 0,
				1, 1, 1, 0, 1, 0, 0, 0,
				- 1, 1, - 1, 0, 0, 1, 0, 0,
				1, 1, - 1, 0, 1, 1, 0, 0,
				// Bottom
				1, - 1, 1, 0, 1, 0, 0, 0,
				- 1, - 1, 1, 0, 0, 0, 0, 0,
				1, - 1, - 1, 0, 1, 1, 0, 0,
				- 1, - 1, - 1, 0, 0, 1, 0, 0
			] ), 8 );

			// Use vertexBuffer, starting at offset 0, 3 items in position attribute
			const positions = new THREE.InterleavedBufferAttribute( vertexBuffer, 3, 0 );
			geometry.setAttribute( 'position', positions );
			// Use vertexBuffer, starting at offset 4, 2 items in uv attribute
			const uvs = new THREE.InterleavedBufferAttribute( vertexBuffer, 2, 4 );
			geometry.setAttribute( 'uv', uvs );

			const indices = new Uint16Array( [
				0, 2, 1,
				2, 3, 1,
				4, 6, 5,
				6, 7, 5,
				8, 10, 9,
				10, 11, 9,
				12, 14, 13,
				14, 15, 13,
				16, 17, 18,
				18, 17, 19,
				20, 21, 22,
				22, 21, 23
			] );

			geometry.setIndex( new THREE.BufferAttribute( indices, 1 ) );

			// material

			const material = new THREE.MeshBasicMaterial();
			material.map = new THREE.TextureLoader().load( 'textures/crate.gif' );
			material.map.colorSpace = THREE.SRGBColorSpace;
			material.map.flipY = false;

			// per instance data

			const matrix = new THREE.Matrix4();
			const offset = new THREE.Vector3();
			const orientation = new THREE.Quaternion();
			const scale = new THREE.Vector3( 1, 1, 1 );
			let x, y, z, w;

			mesh = new THREE.InstancedMesh( geometry, material, instances );

			for ( let i = 0; i < instances; i ++ ) {

				// offsets

				x = Math.random() * 100 - 50;
				y = Math.random() * 100 - 50;
				z = Math.random() * 100 - 50;

				offset.set( x, y, z ).normalize();
				offset.multiplyScalar( 5 ); // move out at least 5 units from center in current direction
				offset.set( x + offset.x, y + offset.y, z + offset.z );

				// orientations

				x = Math.random() * 2 - 1;
				y = Math.random() * 2 - 1;
				z = Math.random() * 2 - 1;
				w = Math.random() * 2 - 1;

				orientation.set( x, y, z, w ).normalize();

				matrix.compose( offset, orientation, scale );

				mesh.setMatrixAt( i, matrix );

			}

			scene.add( mesh );

			renderer = new THREE.WebGLRenderer();
			renderer.setPixelRatio( window.devicePixelRatio );
			renderer.setSize( window.innerWidth, window.innerHeight );
			renderer.setAnimationLoop( animate );
			container.appendChild( renderer.domElement );

			stats = new Stats();
			container.appendChild( stats.dom );

			window.addEventListener( 'resize', onWindowResize );

		}

		function onWindowResize() {

			camera.aspect = window.innerWidth / window.innerHeight;
			camera.updateProjectionMatrix();

			renderer.setSize( window.innerWidth, window.innerHeight );

		}

		//

		function animate() {

			const time = performance.now();

			mesh.rotation.y = time * 0.00005;

			const delta = ( time - lastTime ) / 5000;
			tmpQ.set( moveQ.x * delta, moveQ.y * delta, moveQ.z * delta, 1 ).normalize();
			tmpM.makeRotationFromQuaternion( tmpQ );

			for ( let i = 0, il = instances; i < il; i ++ ) {

				mesh.getMatrixAt( i, currentM );
				currentM.multiply( tmpM );
				mesh.setMatrixAt( i, currentM );

			}

			mesh.instanceMatrix.needsUpdate = true;
			mesh.computeBoundingSphere();

			lastTime = time;

			renderer.render( scene, camera );

			stats.update();

		}

	</script>

</body>

</html>
